module dungeon.tileset;

import derelict.opengl.gl;

import nade.random;
import nade.ast;
import nade.texture;
import nade.loader;


enum TileFace {
	TOP,
	NORTH,
	SOUTH,
	WEST,
	EAST,
}

class TileType {
	protected {
		bool											_spawnable = true;
		string										_id, _name;
		TileType									_base;
		int												_chance;
		bool											_passable;
		real											_height;
		GLuint[TileFace.max + 1]	_textures;
		// Model											_model;
		string[string]						_transitions;
		TileType[]								_variants;
	}

	this()
	{
	}

	bool spawnable() const { return _spawnable; }
	string id() const { return _id; }
	string name() const { return _name; }
	TileType base() { return _base; }
	int chance() const { return _chance; }
	bool passable() const { return _passable; }
	real height() const { return _height; }
	GLuint[] textures() { return _textures; }

	static void install()
	{
		Loader!(TileType).add("chance",
			(ref TileType tile, AST ast, Object[string] state) { tile._chance = ast[0].as!int; }
		);
		Loader!(TileType).add("passable",
			(ref TileType tile, AST ast, Object[string] state) { tile._passable = (ast[0].as!string == "true"); }
		);
		Loader!(TileType).add("height",
			(ref TileType tile, AST ast, Object[string] state) { tile._height = ast[0].as!real; }
		);
		Loader!(TileType).add("texture",
			(ref TileType tile, AST ast, Object[string] state)
			{
				TileFace[] faces;
				switch(ast[0].as!string)
				{
					case "top": faces ~= TileFace.TOP; break;
					case "north": faces ~= TileFace.NORTH; break;
					case "south": faces ~= TileFace.SOUTH; break;
					case "west": faces ~= TileFace.WEST; break;
					case "east": faces ~= TileFace.EAST; break;
					case "sides":
					{
						faces ~= [
							TileFace.NORTH,
							TileFace.SOUTH,
							TileFace.WEST,
							TileFace.EAST
							];
						break;
					}
					case "all":
					{
						faces ~= [
							TileFace.TOP,
							TileFace.NORTH,
							TileFace.SOUTH,
							TileFace.WEST,
							TileFace.EAST
							];
						break;
					}

					default:
				}

				GLuint texture = Texture.load("Textures\\Tiles\\" ~ ast[1].as!string ~ ".bmp");
				foreach(face; faces)
					tile._textures[face] = texture;
			}
		);
		Loader!(TileType).add("variant",
			(ref TileType tile, AST ast, Object[string] state)
			{
				auto variant = new TileType;
				load(variant, ast, state);
				tile._variants ~= variant;
			}
		);
		Loader!(TileType).add("transition",
			(ref TileType tile, AST ast, Object[string] state) { tile._transitions[ast[0].as!string] = ast[1].as!string; }
		);
	}
}


class Tileset {
	protected {
		string		_id, _name;
		int				_depthMin, _depthMax;
		int				_chance;

		TileType[string]	_tiles;
	}

	this()
	{
	}

	TileType getTile(string name)
	{
		assert(name in _tiles, name);
		auto root = _tiles[name];

		TileType[] possibles;
		int chanceTotal = 0;
		void getPossibles(TileType tile)
		{
			if(tile._spawnable)
			{
				possibles ~= tile;
				chanceTotal += tile._chance;
			}

			foreach(variant; tile._variants)
				getPossibles(variant);
		}
		getPossibles(root);

		if(chanceTotal == 0) assert(false, name);

		int r = random(0, chanceTotal);
		foreach(tile; possibles)
		{
			r -= tile._chance;
			if(r < 0) return tile;
		}

		assert(false);
	}

	string id(string rhs) { return _id = rhs; }
	string id() { return _id; }

	string name(string rhs) { return _name = rhs; }
	string name() { return _name; }

	int depthMin(int rhs) { return _depthMin = rhs; }
	int depthMin() { return _depthMin; }

	int depthMax(int rhs) { return _depthMax = rhs; }
	int depthMax() { return _depthMax; }

	int chance(int rhs) { return _chance = rhs; }
	int chance() { return _chance; }

	static void install()
	{
		static Tileset current = null;

		Loader!(Tileset).add("name",
			(ref Tileset tileset, AST ast, Object[string] state)
			{
				tileset._id = ast[0].as!string;
				tileset._name = ast[1].as!string;
			}
		);
		Loader!(Tileset).add("depth",
			(ref Tileset tileset, AST ast, Object[string] state)
			{
				tileset._depthMin = ast[0].as!int;
				tileset._depthMax = ast[1].as!int;
			}
		);
		Loader!(Tileset).add("chance",
			(ref Tileset tileset, AST ast, Object[string] state) { tileset._chance = ast[0].as!int; }
		);

		Loader!(Tileset).add("tile",
			(ref Tileset tileset, AST ast, Object[string] state)
			{
				auto tile = new TileType;

				auto prev = current;
				current = tileset;
				load(tile, ast, state);
				current = prev;

				assert(tile._name.length > 0);
			}
		);

		Loader!(Tileset).add("type",
			(ref Tileset tileset, AST ast, Object[string] state)
			{
				auto tile = new TileType;
				tile._spawnable = false;

				auto prev = current;
				current = tileset;
				load(tile, ast, state);
				current = prev;

				assert(tile._name.length > 0);
			}
		);

		Loader!(TileType).add("name",
			(ref TileType tile, AST ast, Object[string] state)
			{
				tile._id = ast[0].as!string;
				tile._name = ast[1].as!string;
				current._tiles[tile._id] = tile;
			}
		);
		Loader!(TileType).add("base",
			(ref TileType tile, AST ast, Object[string] state)
			{
				assert(current !is null, ast.toString);
				tile._base = current._tiles[ast[0].as!string];
				tile._chance = tile._base._chance;
				tile._passable = tile._base._passable;
				tile._height = tile._base._height;
				tile._textures[] = tile._base._textures[];
				tile._transitions = tile._base._transitions;
			}
		);
	}
}
